#include <stx/imp/cmn.hpp>

inline namespace stx
{
void flushLogs() noexcept
{
    gLogs().enqueue(""s);
}

fs::path const& getExePath()
{
    static auto const ls = []
    {
        auto ret = fs::readSymlink("/proc/self/exe"sv);
        ASSERT(gLastError().isGood());
        return ret;
    }();

    return ls;
}

fs::path const& getExeDir()
{
    static auto const ls = []
    {
        auto ret = fs::readSymlink("/proc/self/exe"sv).parent_path();
        ASSERT(gLastError().isGood());
        return ret;
    }();

    return ls;
}

bool isReadable(Fd const fd) noexcept
{
    auto rfds = ::fd_set{};
    auto tv   = ::timeval{};

    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);

    return ::select(fd + 1, &rfds, nullptr, nullptr, &tv) > 0;
}

bool isWritable(Fd const fd) noexcept
{
    auto wfds = ::fd_set{};
    auto tv   = ::timeval{};

    FD_ZERO(&wfds);
    FD_SET(fd, &wfds);

    return ::select(fd + 1, nullptr, &wfds, nullptr, &tv) > 0;
}

Future<std::uint32_t> watchFsEvents(fs::path const fp, std::uint32_t const mask)
{
    auto const fd = ::inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
    if (fd < 0)
    {
        LOG_ERROR(M_SYS_ERR);
        co_return 0_u32;
    }
    ON_SCOPE_EXIT(::close(fd));

    auto const wd = ::inotify_add_watch(fd, fp.c_str(), mask);
    if (wd < 0)
    {
        LOG_ERROR(M_SYS_ERR);
        co_return 0_u32;
    }
    ON_SCOPE_EXIT(::inotify_rm_watch(fd, wd));

    auto buf = Buffer(C_PAGE_SIZE);

    auto const status = co_await read(fd, buf);
    if (status <= 0)
    {
        LOG_ERROR(M_SYS_ERR);
        co_return 0_u32;
    }

    auto evt = std::bit_cast<::inotify_event const*>(buf.data());
    if (wd != evt->wd)
    {
        LOG_ERROR("Invalid wd: "sv, evt->wd, _I_, wd);
        co_return 0_u32;
    }

    co_return evt->mask;
}

Future<std::ssize_t>
read(Fd const fd, MemSpan const ms, Duration const dur_timeout)
{
    auto const tp_start = SteadyClock::now();

    do
    {
        auto const status = ::read(fd, ms.data(), ms.size());
        if (status >= 0)
        {
            co_return status;
        }

        if (EAGAIN == errno)
        {
            if (dur_timeout > 0ms && elapsed(tp_start) >= dur_timeout)
            {
                co_return C_IO_TIMEOUT;
            }

            auto const fd_state =
                co_await wait(fd, FdState::K_READABLE, dur_timeout);
            if (isReadable(fd_state))
            {
                continue;
            }

            LOG_ERROR("Invalid fd_state: "sv, fd, _I_, fd_state);
            co_return C_INVALID_FD_STATE;
        }

        auto const sys_errno = -errno;
        LOG_ERROR(fd, _I_, M_SYS_ERR);
        co_return sys_errno;
    } while (true);
}

Future<std::ssize_t> read(Fd const fd, MemSpan const ms)
{
    co_return co_await read(fd, ms, 0ms);
}

Future<std::ssize_t>
write(Fd const fd, MemView const mv, Duration const dur_timeout)
{
    auto const tp_start = SteadyClock::now();

    do
    {
        auto const status = ::write(fd, mv.data(), mv.size());
        if (status >= 0)
        {
            co_return status;
        }

        if (EAGAIN == errno)
        {
            if (dur_timeout > 0ms && elapsed(tp_start) >= dur_timeout)
            {
                co_return C_IO_TIMEOUT;
            }

            auto const fd_state =
                co_await wait(fd, FdState::K_WRITABLE, dur_timeout);
            if (isWritable(fd_state))
            {
                continue;
            }

            LOG_ERROR("Invalid fd_state: "sv, fd, _I_, fd_state);
            co_return C_INVALID_FD_STATE;
        }

        auto const sys_errno = -errno;
        LOG_ERROR(fd, _I_, M_SYS_ERR);
        co_return sys_errno;
    } while (true);
}

Future<std::ssize_t> write(Fd const fd, MemView const mv)
{
    co_return co_await write(fd, mv, 0ms);
}

Future<std::ssize_t> read__(Fd const             fd,
                            ::iovec const* const iov,
                            std::size_t const    iov_size,
                            Duration const       dur_timeout)
{
    auto const tp_start = SteadyClock::now();

    do
    {
        auto const status = ::readv(fd, iov, Number(iov_size));
        if (status >= 0)
        {
            co_return status;
        }

        if (EAGAIN == errno)
        {
            if (dur_timeout > 0ms && elapsed(tp_start) >= dur_timeout)
            {
                co_return C_IO_TIMEOUT;
            }

            auto const fd_state =
                co_await wait(fd, FdState::K_READABLE, dur_timeout);
            if (isReadable(fd_state))
            {
                continue;
            }

            LOG_ERROR("Invalid fd_state: "sv, fd, _I_, fd_state);
            co_return C_INVALID_FD_STATE;
        }

        auto const sys_errno = -errno;
        LOG_ERROR(fd, _I_, M_SYS_ERR);
        co_return sys_errno;
    } while (true);
}

Future<std::ssize_t> write__(Fd const             fd,
                             ::iovec const* const iov,
                             std::size_t const    iov_size,
                             Duration const       dur_timeout)
{
    auto const tp_start = SteadyClock::now();

    do
    {
        auto const status = ::writev(fd, iov, Number(iov_size));
        if (status >= 0)
        {
            co_return status;
        }

        if (EAGAIN == errno)
        {
            if (dur_timeout > 0ms && elapsed(tp_start) >= dur_timeout)
            {
                co_return C_IO_TIMEOUT;
            }

            auto const fd_state =
                co_await wait(fd, FdState::K_WRITABLE, dur_timeout);
            if (isWritable(fd_state))
            {
                continue;
            }

            LOG_ERROR("Invalid fd_state: "sv, fd, _I_, fd_state);
            co_return C_INVALID_FD_STATE;
        }

        auto const sys_errno = -errno;
        LOG_ERROR(fd, _I_, M_SYS_ERR);
        co_return sys_errno;
    } while (true);
}

template<IsRaw Dst_>
requires requires
{
    requires IsMemArea<Dst_> && IsResizable<Dst_> &&
        EXPR(1 == sizeof(RangeValueType<Dst_>));
}
[[nodiscard]] inline Future<Dst_> readFileContents__(fs::path const fp)
{
    gLastError().clear();

    auto const file_size = fs::getFileSize(fp);
    if (gLastError().isFailed())
    {
        LOG_ERROR("fs::getFileSize: "sv, fp);
        co_return Dst_{};
    }

    if (file_size > 0)
    {
        auto ret = Dst_{};
        resizeDefaultInit(ret, file_size);
        auto fin = File(fp, File::OpenMode::K_READONLY);
        if (fin)
        {
            auto const status = co_await fin.read(MemSpan(ret), 0ms);
            if (std::cmp_equal(ret.size(), status))
            {
                co_return ret;
            }
        }

        LOG_ERROR("File::File: "sv, file_size, _I_, fp, _I_, M_SYS_ERR);
        co_return Dst_{};
    }

    // For a procfs file, its size is 0 but its contents is not empty.
    auto fin = File(fp, File::OpenMode::K_READONLY);
    if (!fin)
    {
        LOG_ERROR("File::File: "sv, fin.fd(), _I_, fp, _I_, M_SYS_ERR);
        co_return Dst_{};
    }

    auto bufs = std::list<Buffer>{};
    auto mvs  = std::list<MemView>{};

    while (fin)
    {
        auto       buf    = Buffer(C_PAGE_SIZE);
        auto const status = co_await fin.read(buf, 0ms);
        if (status <= 0)
        {
            break;
        }

        buf.resize(Number(status));
        insert(bufs, std::move(buf));
        insert(mvs, bufs.back());
    }

    co_return mcat2<Dst_>(mvs);
}

Future<std::string> readFileToString(fs::path const& fp)
{
    return readFileContents__<std::string>(fp);
}

Future<Buffer> readFileToBuffer(fs::path const& fp)
{
    return readFileContents__<Buffer>(fp);
}

Future<std::ssize_t> appendTo(fs::path const fp, MemView const mv)
{
    auto fout = File(fp, File::OpenMode::K_APPEND);
    co_return co_await fout.write(mv, 0ms);
}

Future<std::ssize_t> writeTo(fs::path const fp, MemView const mv)
{
    fs::remove(fp);
    if (fs::exists(fp))
    {
        co_return -EPERM;
    }

    co_return co_await appendTo(fp, mv);
}

Future<> coroLogCleaner__(fs::path             fp_log,
                          MaxLogBackupCount    max_log_backup_cnt,
                          MaxLogFileSize const max_log_file_size)
{
    auto const fp_log_dir = fp_log.parent_path();
    auto const s_log_file_name =
        regexReplace(fp_log.filename().native(), rgx<R"(\.)">(), R"(\.)"_sv);
    auto const s_rgx =
        scat(R"(^(.+/)?)"sv, s_log_file_name, R"(\.bak\.[1-9][0-9]+$)"sv);
    static auto const rgx = makeRegex(s_rgx);

    if (max_log_backup_cnt < 1)
    {
        max_log_backup_cnt = MaxLogBackupCount(1);
    }

    auto const fn_clean = [&]
    {
        auto fp_baks = Set<fs::path>{};

        for (auto const& fp_bak_entry : fs::getFileRange(fp_log_dir, rgx))
        {
            insert(fp_baks, fp_bak_entry.path());
        }

        while (fp_baks.size() > max_log_backup_cnt)
        {
            fs::remove(*fp_baks.begin());
            fp_baks.erase(fp_baks.begin());
        }

        auto const space_info = fs::getSpaceInfo(fp_log_dir);
        if (isMax(space_info.free))
        {
            LOG_ERROR("Failed to query space info: "sv, fp_log);
            return;
        }

        if (space_info.available < max_log_file_size * 3)
        {
            for (auto const& fp_bak : fp_baks)
            {
                fs::remove(fp_bak);
            }

            return;
        }
    };

    while (true)
    {
        fn_clean();
        co_await 1min;
    }
}

void thrdLogger__(fs::path                  fp_log,
                  MaxLogBatchSize           max_batch_size,
                  MaxLogFlushInterval const max_flush_interval,
                  MaxLogFileSize const      max_log_file_size)
{
    auto constexpr sv_newline           = "\n"sv;
    auto constexpr max_batch_size_limit = MaxLogBatchSize(IOV_MAX);

    gLoggerIsEnabled__() = true;

    auto s_msg         = std::string{};
    auto ss            = std::deque<std::string>{};
    auto mvs           = std::deque<MemView>{};
    auto ts_last_flush = SteadyClock::now();

    if (max_batch_size > max_batch_size_limit)
    {
        max_batch_size = max_batch_size_limit;
    }

    while (true)
    {
        gLogs().wait_dequeue(s_msg);
        auto const is_flush_msg = s_msg.empty();

        insert(ss, std::move(s_msg));
        insert(mvs, ss.back());
        insert(mvs, sv_newline);

        if (is_flush_msg || elapsed(ts_last_flush) > max_flush_interval ||
            mvs.size() >= max_batch_size)
        {
            appendTo(fp_log, mvs).get();

            if (fs::getFileSize(fp_log) > max_log_file_size)
            {
                fs::rename(
                    fp_log,
                    scat(fp_log.native(),
                         ".bak."sv,
                         getDateTimeText(DateTimeFmt::K_COMMON_COMPACT)));
            }

            ss.clear();
            mvs.clear();
            ts_last_flush = SteadyClock::now();
        }
    }
}

void createLogThread(fs::path                  fp_log,
                     MaxLogBatchSize const     max_batch_size,
                     MaxLogFlushInterval const max_flush_interval,
                     MaxLogBackupCount const   max_log_backup_cnt,
                     MaxLogFileSize const      max_log_file_size)
{
    [[maybe_unused]] static auto ls = [&]
    {
        coroLogCleaner__(fp_log, max_log_backup_cnt, max_log_file_size);

        createThread("logger"s,
                     thrdLogger__,
                     std::move(fp_log),
                     max_batch_size,
                     max_flush_interval,
                     max_log_file_size);
        return 0;
    }();
}

FileDescriptor::~FileDescriptor()
{
    this->close();
}

FileDescriptor::FileDescriptor(Fd const fd) : fd_(fd)
{}

FileDescriptor::operator bool() const noexcept
{
    return this->fd_ >= 0;
}

std::size_t FileDescriptor::getHashCode() const noexcept
{
    return Number(this->fd_);
}

Fd FileDescriptor::fd() const noexcept
{
    return this->fd_;
}

void FileDescriptor::close() noexcept
{
    if (this->fd_ >= 0)
    {
        ::close(this->fd_);
        this->fd_ = C_UNDEFINED;
    }
}

Future<std::ssize_t> BiPipe::read(MemSpan const  ms,
                                  Duration const dur_timeout) noexcept
{
    return stx::read(this->fd_, ms, dur_timeout);
}

Future<std::ssize_t> BiPipe::write(MemView const  mv,
                                   Duration const dur_timeout) noexcept
{
    return stx::write(this->fd_, mv, dur_timeout);
}

File::File(fs::path const& fp, OpenMode const open_mode) noexcept
    : File(fp,
           open_mode,
           OpenMode::K_APPEND == open_mode ? Position::K_END
                                           : Position::K_BEGIN,
           0)
{}

File::File(fs::path const& fp,
           OpenMode const  open_mode,
           Position const  whence) noexcept
    : File(fp,
           open_mode,
           OpenMode::K_APPEND == open_mode ? Position::K_END : whence,
           0)
{}

File::File(fs::path const&    fp,
           OpenMode const     open_mode,
           Position const     whence,
           std::ssize_t const offset) noexcept
    : open_mode_(open_mode)
{
    auto flags = O_NONBLOCK | O_CLOEXEC | O_LARGEFILE;
    switch (open_mode)
    {
    case OpenMode::K_READONLY:
        flags |= O_RDONLY;
        break;
    case OpenMode::K_APPEND:
        flags |= O_APPEND | O_RDWR | O_CREAT;
        break;
    case OpenMode::K_MODIFY:
        flags |= O_RDWR | O_CREAT;
        break;
    }

    // The mode argument must be supplied when O_CREAT is specified in flags.
    this->fd_ = ::open(fp.c_str(), flags, S_IRWXU | S_IRWXG | S_IRWXO);
    if (this->fd_ < 0)
    {
        auto const sys_errno = -errno;
        LOG_ERROR("::open: "sv, fp, _I_, open_mode, _I_, M_SYS_ERR);
        this->fd_ = sys_errno;
    }

    if (OpenMode::K_APPEND == this->open_mode_)
    {
        this->seek(Position::K_END, 0);
    }
    else
    {
        this->seek(whence, offset);
    }
}

std::ssize_t File::seek(Position const     whence,
                        std::ssize_t const offset) noexcept
{
    switch (whence)
    {
    case Position::K_BEGIN:
        return ::lseek64(this->fd_, offset, SEEK_SET);
    case Position::K_END:
        return ::lseek64(this->fd_, offset, SEEK_END);
    default:
        return ::lseek64(this->fd_, offset, SEEK_CUR);
    }
}

std::ssize_t File::seek(Position const whence) noexcept
{
    return this->seek(whence, 0);
}

std::ssize_t File::seek(std::ssize_t const offset) noexcept
{
    return this->seek(Position::K_CURRENT, offset);
}

Status File::flush() noexcept
{
    return ::fsync(this->fd_);
}

TimerFd::TimerFd(Duration const dur_initial_expiration,
                 Duration const dur_repeated_expiration) noexcept
{
    this->reset(dur_initial_expiration, dur_repeated_expiration);
}

TimerFd::TimerFd(Duration const dur) noexcept
{
    this->reset(dur);
}

void TimerFd::clear()
{
    auto n = std::uint64_t{};
    ::read(this->fd_, &n, sizeof(n));
}

/*
If the timer has already expired one or more times since its settings were last
modified using timerfd_settime(), or since the last successful read(2), then the
buffer given to read(2) returns an unsigned 8-byte integer (uint64_t) containing
the number of expirations that have occurred. (The returned value is in host 
byte order, i.e., the native byte order for integers on the host machine.)

If no timer expirations have occurred at the time of the read(2), then the call
either blocks until the next timer expiration, or fails with the error EAGAIN if
the file descriptor has been made nonblocking (via the use of the fcntl(2) 
F_SETFL operation to set the O_NONBLOCK flag).

A read(2) will fail with the error EINVAL if the size of the supplied buffer is
less than 8 bytes.
*/
bool TimerFd::reset(Duration const dur_initial_expiration,
                    Duration const dur_repeated_expiration) noexcept
{
    if (this->fd_ < 0)
    {
        this->fd_ =
            ::timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK | TFD_CLOEXEC);
        if (this->fd_ < 0)
        {
            LOG_ERROR("::timerfd_create(CLOCK_BOOTTIME): "sv, M_SYS_ERR);
            return false;
        }
    }

    auto its = ::itimerspec{};

    its.it_value.tv_sec = dur_initial_expiration.ms.count() / 1000_i64;
    its.it_value.tv_nsec =
        (dur_initial_expiration.ms.count() % 1000_i64) * 1000000_i64;

    its.it_interval.tv_sec = dur_repeated_expiration.ms.count() / 1000_i64;
    its.it_interval.tv_nsec =
        (dur_repeated_expiration.ms.count() % 1000_i64) * 1000000_i64;

    this->clear();

    if (::timerfd_settime(this->fd_, 0, &its, nullptr) < 0)
    {
        LOG_ERROR("::timerfd_settime: "sv,
                  its.it_value.tv_sec,
                  _I_,
                  its.it_value.tv_nsec,
                  _I_,
                  M_SYS_ERR);
        this->close();
        return false;
    }

    return true;
}

bool TimerFd::reset(Duration const dur_repeated_expiration) noexcept
{
    return this->reset(dur_repeated_expiration, dur_repeated_expiration);
}

EventFd::EventFd() noexcept : EventFd(0, false)
{}

EventFd::EventFd(std::size_t const init_value, bool const is_semaphore)
    : FileDescriptor(::eventfd(Number(init_value),
                               EFD_NONBLOCK | EFD_CLOEXEC |
                                   EXPR(is_semaphore ? EFD_SEMAPHORE : 0)))
{}

EventFd::EventFd(std::size_t const init_value) : EventFd(init_value, false)
{}

void EventFd::clear()
{
    if (*this)
    {
        auto n = std::uint64_t{};
        while (::read(this->fd_, &n, 8) == 8)
        {}

        if (EAGAIN != errno)
        {
            LOG_ERROR("Failed to clear eventfd: "sv, this->fd_, _I_, M_SYS_ERR);
        }
    }
}

std::uint64_t EventFd::read()
{
    auto n = std::uint64_t{};
    if (::read(this->fd_, &n, 8) < 0)
    {
        return max(n);
    }

    return n;
}

bool EventFd::write(std::uint64_t const n)
{
    ASSERT(!isMax(n));
    return 8 == ::write(this->fd_, &n, 8);
}

bool EventFd::notify()
{
    return this->write(1);
}

FileMutex::~FileMutex()
{
    this->unlock();
}

bool FileMutex::lock(fs::path const& fp_lock)
{
    this->unlock();

    this->fd_ =
        Fd(::open(fp_lock.c_str(),
                  O_CREAT | O_TRUNC | O_CLOEXEC,
                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH));
    if (this->fd_ < 0)
    {
        RUNTIME_ERROR("Failed to lock file mutex "sv, fp_lock, _I_, M_SYS_ERR);
    }

    if (-1 == ::flock(this->fd_, LOCK_NB | LOCK_EX))
    {
        this->close();
        return false;
    }

    return true;
}

void FileMutex::unlock()
{
    if (this->fd_ >= 0)
    {
        ::flock(this->fd_, LOCK_NB | LOCK_UN);
        this->close();
    }
}
} // namespace stx

/*
Notes:

The gethostbyname() and gethostbyaddr() functions are deprecated on most
platforms, and they don't implement support for IPv6. IPv4 has reached its
limits, the world has been moving to IPv6 for awhile now. Use getaddrinfo()
and getnameinfo() instead, respectively.

For asynchronous network address and service translation, use:

- getaddrinfo_a()
- gai_suspend()
- gai_error()
- gai_cancel()

Best Practices:

[*] A Tcp Connection is always actively shut down by the client side.

[*] The server side always sets SO_REUSEADDR|SO_REUSEPORT.

[*] The client side always sets SO_LINGER to reset the connection iff having
    received all expected data and no data to send.

[*] If the server side decided to close a connection that is timed out, use
    RST as the client side does.
*/